import os
import sys
import time
from math import floor

import numpy as np

from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThread, QThreadPool, QTimer
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QDialog, QMainWindow, QFileDialog, QApplication, QMessageBox, QLCDNumber, QSplashScreen, \
    QAction, QTextBrowser


from ui.main_ui_python import *
from hsa_python.my_ga_zero_merge_stop import *


data_file = None  # 数据集文件
parent_file = None  # 父结点文件
node_file = None  # 结点取值数量文件
result_path = None  # 采样结果文件存储路径
cur_ws = os.getcwd()  # 记录当前工作路径
data_dir = cur_ws.replace("\\", "/")  # 记录数据集文件
tb1 = None
tb2 = None


# data_file = "E:/PyCharm/test_exe/data/asia/data_20000.txt"
# parent_file = "E:/PyCharm/test_exe/data/asia/parent.txt"
# node_file = "E:/PyCharm/test_exe/data/asia/nodeValueNum.txt"


# 这样，信号可以定义在类的构造函数__init__中
class NewSignal(QObject):
    signal = pyqtSignal()


class SampleThread(QRunnable):

    def __init__(self, name):
        super(SampleThread, self).__init__()
        self.name = name
        # 继承自QRunnable的线程类必须这样写
        # 不能像继承QThread的线程类那样写在任何函数外面
        self.start_signal = NewSignal()

    def run(self):
        global log, data_file, parent_file, node_file, result_path, cur_ws
        global tb1, tb2
        print("SampleThread中run函数正在运行", flush=True)
        print("SampleThread", QThread().currentThreadId(), flush=True)
        result_path = os.path.split(cur_ws)[0] + "\\data\\result"
        result_path = result_path.replace("\\", "/")
        if not os.path.exists(result_path):
            os.makedirs(result_path)
        # 读取初始数据集文件
        D = np.loadtxt(data_file, dtype=int)
        # 读取结点数量文件
        nodeValueNum = np.loadtxt(node_file, dtype=int).reshape((1, D.shape[1]))
        # 读取父结点文件
        parent = np.loadtxt(parent_file, dtype=int)
        # 父结点数量
        parentNum = getParentNum(parent)
        # 每个结点的父结点的取值情况的数量
        totalC = getParentCombination(parent, nodeValueNum)
        maxN = np.max(nodeValueNum)
        maxT = np.max(totalC)
        # 样本数量
        M = D.shape[0]
        # 贝叶斯网络结点数量
        n = D.shape[1]
        # 根据数据集D，将父结点编码，用于下述的preProcess
        idxJ = encodeParent(D, parent, parentNum, nodeValueNum)
        # 数据集D的分布参数theta和eta
        theta, eta = preProcess(D, idxJ, parent, nodeValueNum)
        # 每次抽取的样本数量
        delta = 10
        # 计数器
        cnt = 1
        # 最大重复次数
        max_dup = 30
        # 前一次评分
        pre_chiScore = -1
        # 抽取出来的子样本
        subSample = None
        # 子样本数量
        subSampleNum = 0
        m = np.zeros((n, maxT, maxN), dtype=int)
        for h in range(floor(M / delta) - 1):
            tb1.append('''--------------------------------------------------
第%d次迭代开始
剩余数据集大小：(%d, %d)''' % (h, M-subSampleNum, n))
            
            # index表示被选中个体在样本D中的样本编号
            index, chiScore, dScore, zero_chiScore, node_chiScore \
                = GA(m, theta, eta, D, maxT, maxN, parent,
                     parentNum, nodeValueNum, subSampleNum, M, delta)
            # 经过遗传算法GA选择出来的个体
            deltaD = D[tuple(index[0, :]), :]
            # 根据数据集deltaD，将父结点取值编码
            idxJ = encodeParent(deltaD, parent, parentNum, nodeValueNum)
            # 计算deltaD的分布参数m
            mAsterisk = distCal(deltaD, idxJ, maxT, maxN)
            # 将选择出的数据集deltaD加入数据集subSample后的分布参数m
            m += mAsterisk
            # 将数据集deltaD加入数据集subSample
            if subSample is None:
                subSample = deltaD
            else:
                subSample = np.concatenate((subSample, deltaD), axis=0)
            # 将数据集deltaD从数据集D种剔除
            D = np.delete(D, tuple(index[0, :]), axis=0)
            # 数据集subSample样本容量增加
            subSampleNum += delta
            tb1.append('''子样本的chiScore: %d
子样本的dScore: %f
子样本的zero_chiScore: %d
子样本的node_chiScore: %d
子样本的大小: %d
子样本的delta：%f''' % (chiScore, dScore, zero_chiScore, node_chiScore, subSampleNum, validation(theta, m, n, totalC, nodeValueNum)))

            if chiScore == pre_chiScore:
                cnt += 1
            else:
                pre_chiScore = chiScore
                cnt = 1
            tb1.append("计数器：%d" % cnt)
            if chiScore == 0 or cnt > max_dup:
                tb1.append("第%d次迭代结束" % h)
                
                break
            tb1.append("第%d次迭代结束" % h)
            # tb1.moveCursor(tb1.textCursor().End)
        # 验证subSample的theta和eta，以及distCal的返回值
        idxJ = encodeParent(subSample, parent, parentNum, nodeValueNum)
        theta, eta = preProcess(subSample, idxJ, parent, nodeValueNum)
        mA = distCal(subSample, idxJ, maxT, maxN)

        # zero_count = 0
        # for i in range(n):
        #     if totalC[0, i] == 0:
        #         totalC[0, i] = 1
        # for i in range(n):
        #     tb1.append("--------------------------------------------------")
        #     for j in range(totalC[0, i]):
        #         temp = "结点%d, 父结点取值%d:" % (i + 1, j + 1)
        #         count2 = 0
        #         for k in range(nodeValueNum[0, i]):
        #             if theta[i, j, k] == 0:
        #                 count2 += 1
        #             temp = " ".join((temp, str(theta[i, j, k])))
        #         tb1.append(temp)
        #
        #         if count2 == nodeValueNum[0, i]:
        #             zero_count += 1
        # tb1.append("--------------------------------------------------")
        # tb1.append("全零的数量：%d" % zero_count)
        
        idxJ = encodeParent(D, parent, parentNum, nodeValueNum)
        theta, _ = preProcess(D, idxJ, parent, nodeValueNum)
        tb1.append('''抽样算法执行成功
--------------------------------------------------
采样后的子样本的大小：(%d, %d)
平均分布差异：%f
--------------------------------------------------
        ''' % (subSample.shape[0], subSample.shape[1], validation(theta, mA, D.shape[1], totalC, nodeValueNum)))
        # tb1.append("delta: %f" % validation(theta, mA, D.shape[1], totalC, nodeValueNum))
        # 保存采样算法结果
        np.savetxt(result_path + "/subsample.txt", subSample, "%d")
        # 发射采样结束信号
        self.start_signal.signal.emit()


class UiWithSignal(MainUi):
    # 采样算法完成，通知等待窗口结束
    stop_signal = pyqtSignal()
    # 三个相关配置文件未完成，通知操作者进行配置
    none_signal = pyqtSignal()
    # 采样算法错误结束
    error_end_signal = pyqtSignal()

    def __init__(self):
        super(UiWithSignal, self).__init__()

        global tb1, tb2
        tb1 = self.tb1
        tb2 = self.tb2

        # 创建线程池
        self.pool = QThreadPool()
        # 全局线程
        self.pool.globalInstance()
        # 线程池中最大的线程数量
        self.pool.setMaxThreadCount(10)
        # 初始化信号
        self.init_connect()

        # 创建菜单栏MenuBar
        self.menu_bar = self.menuBar()
        self.file = self.menu_bar.addMenu("文件")
        self.open_data_action = QAction(qtawesome.icon("fa.file-text"), "打开数据集文件", self)
        self.open_data_action.setStatusTip("打开数据集文件")
        self.open_data_action.setShortcut("Ctrl+Shift+I")
        self.open_data_action.triggered.connect(self.slot_get_data)
        self.file.addAction(self.open_data_action)
        # 打开结点取值文件
        self.open_node_action = QAction(qtawesome.icon("fa.file-text"), "打开结点取值文件", self)
        self.open_node_action.setStatusTip("打开结点取值文件")
        self.open_node_action.setShortcut("Ctrl+Shift+O")
        self.open_node_action.triggered.connect(self.slot_get_node)
        self.file.addAction(self.open_node_action)
        # 打开父结点文件
        self.open_parent_action = QAction(qtawesome.icon("fa.file-text"), "打开父结点文件", self)
        self.open_parent_action.setStatusTip("打开父结点文件")
        self.open_parent_action.setShortcut("Ctrl+Shift+P")
        self.open_parent_action.triggered.connect(self.slot_get_parent)
        self.file.addAction(self.open_parent_action)
        self.file.addSeparator()
        # 保存
        self.save_as_action = QAction(qtawesome.icon("fa.save"), "另存为", self)
        self.save_as_action.setStatusTip("保存结果")
        self.save_as_action.setShortcut("Ctrl+S")
        self.save_as_action.triggered.connect(self.save_result)
        self.file.addAction(self.save_as_action)
        self.file.addSeparator()
        # 退出
        self.exit_action = QAction(qtawesome.icon("fa.window-close"), "退出", self)
        self.exit_action.setStatusTip("退出程序")
        self.exit_action.setShortcut("Ctrl+Shift+Q")
        self.exit_action.triggered.connect(self.exit_prog)
        self.file.addAction(self.exit_action)

        self.run = self.menu_bar.addMenu("运行")
        # 退出
        self.sample_action = QAction(qtawesome.icon("fa.play"), "抽样", self)
        self.sample_action.setStatusTip("抽样")
        self.sample_action.setShortcut("Ctrl+Shift+S")
        self.sample_action.triggered.connect(self.sample)
        self.run.addAction(self.sample_action)

        self.about = self.menu_bar.addMenu("关于")

        self.menu_bar.setStyleSheet('''
            QMenuBar{
                
            }
        ''')

    def init_connect(self):
        """
        初始化一些信号
        """
        # 三个配置文件信号与槽
        self.btn_data.clicked.connect(self.slot_get_data)
        self.btn_parent.clicked.connect(self.slot_get_parent)
        self.btn_node.clicked.connect(self.slot_get_node)
        # 按钮btn_sample单击触发采样算法线程sample
        self.btn_sample.clicked.connect(self.sample)
        # 采样算法完成，显示通知窗口
        self.stop_signal.connect(self.notice_dialog)
        # 保存结果
        self.btn_save.clicked.connect(self.save_result)
        # 加载结果
        self.btn_load.clicked.connect(self.load_result)

    def slot_get_data(self):
        """
        读取数据集文件
        """
        global data_file, data_dir
        data_file, _ = QFileDialog.getOpenFileName \
            (self, "打开数据文件", data_dir, "文本文件(*.txt)")
        if data_file != "":
            data_dir = os.path.split(data_file)[0]
            self.tb1.append("打开数据文件：" + data_file)
        else:
            print("未正确打开数据集文件data_file，请重新打开")
            data_file = None
            self.tb1.append("未正确打开数据集文件，请重新打开!")

    def slot_get_parent(self):
        """
        读取父结点文件
        """
        global parent_file, data_dir
        parent_file, _ = QFileDialog.getOpenFileName \
            (self, "打开父结点文件", data_dir, "文本文件(*.txt)")
        if parent_file != "":
            data_dir = os.path.split(parent_file)[0]
            self.tb1.append("打开父结点文件：" + parent_file)
        else:
            print("未正确打开父结点文件parent_file，请重新打开")
            parent_file = None
            self.tb1.append("未正确打开父结点文件，请重新打开!")

    def slot_get_node(self):
        """
        读取结点数量文件
        """
        global node_file, data_dir
        node_file, _ = QFileDialog.getOpenFileName \
            (self, "打开结点取值数量文件", data_dir, "文本文件(*.txt)")
        if node_file != "":
            data_dir = os.path.split(node_file)[0]
            self.tb1.append("打开结点取值文件：" + node_file)
        else:
            print("未正确打开结点取值文件node_file，请重新打开")
            node_file = None
            self.tb1.append("未正确打开结点取值文件，请重新打开!")

    def save_result(self):
        global data_dir
        result = self.tb2.toPlainText()
        file_name, _ = QFileDialog.getSaveFileName(self, "文件保存", data_dir, "文本文件(*.txt);;全部文件(*.*)")
        if file_name != "":
            fp = open(file_name, "w")
            fp.write(result)

    def load_result(self):
        if result_path is not None and os.path.exists(result_path + "/subsample.txt"):
            with open(result_path + "/subsample.txt", "r") as fp:
                tb2.append(fp.read())
        else:
            error_msg_box = QMessageBox()
            error_msg_box.setWindowIcon(QIcon("../icon/sample256.ico"))
            error_msg_box.setWindowTitle("加载错误")
            error_msg_box.setIconPixmap(QPixmap("../icon/warning64.ico"))
            error_msg_box.setText("还未进行抽样，请抽样后再加载结果")
            error_msg_box.setStandardButtons(QMessageBox.Ok)
            btn_ok = error_msg_box.button(QMessageBox.Ok)
            btn_ok.setText("确定")
            error_msg_box.exec_()

    def sample(self):
        """
        采样算法
        """
        global data_file, parent_file, node_file, cur_ws
        print("sample函数正在运行", flush=True)
        self.btn_data.setDisabled(True)
        self.btn_node.setDisabled(True)
        self.btn_parent.setDisabled(True)
        self.btn_save.setDisabled(True)
        self.btn_load.setDisabled(True)
        self.btn_sample.setDisabled(True)
        # 若三个配置文件至少有一个没有配置，则警告
        if data_file is None or parent_file is None or node_file is None:

            self.btn_data.setDisabled(False)
            self.btn_node.setDisabled(False)
            self.btn_parent.setDisabled(False)
            self.btn_save.setDisabled(False)
            self.btn_load.setDisabled(False)
            self.btn_sample.setDisabled(False)

            error_msg_box = QMessageBox()
            error_msg_box.setWindowIcon(QIcon("../icon/sample256.ico"))
            error_msg_box.setWindowTitle("配置文件错误")
            error_msg_box.setIconPixmap(QPixmap("../icon/warning64.ico"))
            error_msg_box.setText("请查看三个配置文件是否正确！")
            error_msg_box.setStandardButtons(QMessageBox.Ok)
            btn_ok = error_msg_box.button(QMessageBox.Ok)
            btn_ok.setText("确定")
            error_msg_box.exec_()
            self.none_signal.emit()
            print("由于三个配置文件未完成，sample函数提前运行结束", flush=True)
            return
        # 清除文本框信息
        self.tb1.clear()
        # 采样算法线程
        sample_thread = SampleThread("sample")
        sample_thread.start_signal.signal.connect(self.notice_dialog)
        # 开启线程
        self.pool.start(sample_thread)
        print("sample函数正在结束", flush=True)

    def notice_dialog(self):
        """
        程序运行成功的通知窗口
        """
        global data_file, node_file, parent_file
        data_file = None
        node_file = None
        parent_file = None
        self.btn_data.setDisabled(False)
        self.btn_node.setDisabled(False)
        self.btn_parent.setDisabled(False)
        self.btn_save.setDisabled(False)
        self.btn_load.setDisabled(False)
        self.btn_sample.setDisabled(False)
        notice_msg_box = QMessageBox()
        notice_msg_box.setWindowIcon(QIcon("../icon/sample256.ico"))
        notice_msg_box.setWindowTitle("成功通知")
        notice_msg_box.setIconPixmap(QPixmap("../icon/information64.ico"))
        notice_msg_box.setText("程序运行成功，请查看结果。")
        notice_msg_box.setStandardButtons(QMessageBox.Ok)
        btn_ok = notice_msg_box.button(QMessageBox.Ok)
        btn_ok.setText("确定")
        notice_msg_box.exec_()

    # 添加中文的确认退出提示框
    def closeEvent(self, event):
        # 创建一个消息盒子（提示框）
        quit_msg_box = QMessageBox()
        # 设置左上角图标
        quit_msg_box.setWindowIcon(QIcon("../icon/sample256.ico"))
        # 设置提示框的标题
        quit_msg_box.setWindowTitle('确认提示')
        # 设置提示框的内容
        quit_msg_box.setText('你确认退出吗？')
        # 设置确认标志
        quit_msg_box.setIconPixmap(QPixmap("../icon/ack64.ico"))
        # 设置按钮标准，一个yes一个no
        quit_msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        # 获取两个按钮并且修改显示文本
        btn_yes = quit_msg_box.button(QMessageBox.Yes)
        btn_yes.setText('确定')
        btn_no = quit_msg_box.button(QMessageBox.No)
        btn_no.setText('取消')
        quit_msg_box.exec_()
        print("退出事件")
        # 判断返回值，如果点击的是Yes按钮，我们就关闭组件和应用，否则就忽略关闭事件
        if quit_msg_box.clickedButton() == btn_yes:
            event.accept()
            print("退出程序，运行结束")
            os._exit(0)  # 结束整个程序
        else:
            event.ignore()
            print("取消退出程序")

    def exit_prog(self):
        # 创建一个消息盒子（提示框）
        quit_msg_box = QMessageBox()
        # 设置左上角图标
        quit_msg_box.setWindowIcon(QIcon("../icon/sample256.ico"))
        # 设置提示框的标题
        quit_msg_box.setWindowTitle('确认提示')
        # 设置提示框的内容
        quit_msg_box.setText('你确认退出吗？')
        # 设置确认标志
        quit_msg_box.setIconPixmap(QPixmap("../icon/ack64.ico"))
        # 设置按钮标准，一个yes一个no
        quit_msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        # 获取两个按钮并且修改显示文本
        btn_yes = quit_msg_box.button(QMessageBox.Yes)
        btn_yes.setText('确定')
        btn_no = quit_msg_box.button(QMessageBox.No)
        btn_no.setText('取消')
        quit_msg_box.exec_()
        print("退出事件")
        # 判断返回值，如果点击的是Yes按钮，我们就关闭组件和应用，否则就忽略关闭事件
        if quit_msg_box.clickedButton() == btn_yes:
            print("退出程序，运行结束")
            os._exit(0)  # 结束整个程序
        else:
            print("取消退出程序")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    pixmap = QPixmap("../icon/sample.png")
    splash = QSplashScreen(pixmap)
    splash.show()
    app.processEvents()
    win = UiWithSignal()
    splash.finish(win)
    win.show()
    sys.exit(app.exec_())
